/**
 * mLibVec4D.js
 * Created: April 2013
 * Source link: http://code.google.com/p/code-depository/source/browse/
 *
 * By Suresh Nalluri,
 * License: The MIT License (MIT)
 *          See LICENSE details
  *
 * Requires:
 *	mLibFactory.js
 *	mLibIncludes.js
 *
 *	Todo:
 *		1. may have to create a new Class for Point4 as we have no way of distinguishing
 *		  a point from vector.
 *
 * 4D Vector Class
 * A 4D Vector is composed of x, y, z, w components
 *
 *
 * note this constructor pattern allows us to create objects in 2 ways:
 * 1. var o = mLib.Vec4(x, y, z,w);      //OR
 * 2. var o = new mLib.Vec4(x,y,z,w);
 *
 * NOTE: var o = [new] mLIB.Vec4(<no params>); // will return default vector (1.0, 0.0, 0.0);
 *
 * added benefit of this pattern is that we get a copy constructor by default!!
 * eg. var newVec = mLIB.Vec4(existingVec);
 * so no need to have an explicit copyOf method.
 *
 * Usage: 
 *		var o = [new] mLIB.Vec4([ x,y,z,w | Vec4 | [] ])
 *
 *-------------------------------------------------------
 **/

if (typeof mLIB == 'undefined') function mLIB() {};


( function(module) {

	module.Vec4 = Factory.obj( {
        init: function() {
            var nargs = arguments.length;
            
            if ( !nargs) {
                // no arguments in constructor. set to default vector
                this.x = 1.0;
                this.y = this.z = this.w = 0.0;
            } else if ( nargs == 1 )
            {
                // we are copying an existing vector
                this.x = arguments[0].x;
                this.y = arguments[0].y;
                this.z = arguments[0].z;
                this.w = arguments[0].w;
            } else {
                // called with x,y,z,w params
                this.x = arguments[0];
                this.y = arguments[1];     
                this.z = arguments[2];     
                this.w = arguments[3];     
            }

        }, //init()
        
        // length - measured from the origin (0,0,0)
        length: function() {
            var x = this.x,
                y = this.y,
                z = this.z,
                w = this.w;
            return module.Includes.sqrt( x*x + y*y + z*z  + w*w );
        },
        
        lengthSquared: function() {
            var x = this.x,
                y = this.y,
                z = this.z,
                w = this.w;
            return x*x + y*y + z*z + w*w;
        },

        // tests for equality
        isEqual : function(v) { 
            return (
				mLIB.Includes.isZero(this.x - v.x) &&
				mLIB.Includes.isZero(this.y - v.y) &&
				mLIB.Includes.isZero(this.z - v.z) &&
				mLIB.Includes.isZero(this.w - v.w)
            );

        },
        
        isNotEqual: function(v) {
            return !(
				mLIB.Includes.isZero(this.x - v.x) &&
				mLIB.Includes.isZero(this.y - v.y) &&
				mLIB.Includes.isZero(this.z - v.z) &&
				mLIB.Includes.isZero(this.w - v.w)
            );
        },

        //test for zero vector
        isZero: function() {
            var x = this.x,
                y = this.y,
                z = this.z,
                w = this.w;
            return Ml.isZero( x*x + y*y + z*z + w*w);
        },
        
        // test for unit vector
        isUnit: function() {
            var x = this.x,
                y = this.y,
                z = this.z,
                w = this.w;
            return mLIB.Includes.isZero( 1.0 - x*x - y*y - z*z - w*w );
        },

        // zero the elements of self that are close to zero.
        roundToZero: function() {
            module.Includes.isZero( this.x ) && (this.x = 0.0);
            module.Includes.isZero( this.y ) && (this.y = 0.0);
            module.Includes.isZero( this.z ) && (this.z = 0.0);
            module.Includes.isZero( this.w ) && (this.w = 0.0);
            return this;
        },


        // set all elements to zero
        zero: function() {
            this.x = this.y = this.z = this.w = 0.0;
            return this;
        },
    
        // normalise self
        normalise: function() {
            var x = this.x,
                y = this.y,
                z = this.z,
                w = this.w,
                lsq = x*x + y*y +z*z + w*w;
            
            if ( module.Includes.isZero(lsq) ) {
                this.x = this.y = this.z = this.w = 0.0;
            } else
            {
                var scale = 1 / mLIB.Includes.sqrt( lsq );
                this.x *= scale;
                this.y *= scale;
                this.z *= scale;
                this.w *= scale;
            }
            return this;
        },

        // return a new vector, which is the Normalised version of this.
        Normalise: function() {
            var x = this.x,
                y = this.y,
                z = this.z,
                w = this.w,
                lsq = x*x + y*y +z*z + w*w;
            
            if ( mLIB.Includes.isZero(lsq) ) {
                return module.Vec3(0, 0, 0);
            } else
            {
                var scale = 1.0 / mLIB.Includes.sqrt( lsq );
				return mLIB.Vec4(x*scale, y*scale, z*scale, w*scale);
           }
        },

        // add a vector to self
        plus: function(v) {
            this.x += v.x;
            this.y += v.y;
            this.z += v.z;
            this.w += v.w;
            return this;
        },
        
        //return a new vector made  of self + v
        Plus: function(v) {
            return new module.Vec4(this.x+v.x, this.y+v.y, this.z+v.z, this.w+v.w);
        },

        // subtract vector from self
        minus: function(v) {
            this.x -= v.x;
            this.y -= v.y;
            this.z -= v.z;
            this.w -= v.w;
            return this;
        },
        
        //return a new vector made  of self - v
        Minus: function(v) {
            return new module.Vec4(this.x-v.x, this.y-v.y, this.z-v.z, this.w-v.w);
        },

        // set the vector as the negation of self
        negation: function() {
            this.x = -this.x;
            this.y = -this.y;
            this.z = -this.z;
            this.w = -this.w;
            return this;
        },
        
        // return a new vector which is the negation of self
        Negation: function() {
            return new module.Vec4(-this.x, -this.y, -this.z, -this.w);
        },

        // scale self - scalar multiplication
        scaleUp: function(scale) {
            this.x *= scale;
            this.y *= scale;
            this.z *= scale;
            this.w *= scale;
            return this;
        },

        // scale self and return as new vector - scalar multiplication
        ScaleUp: function(scale) {
            return new module.Vec4(this.x*scale, this.y*scale, this.z*scale, this.w*scale);
        },

        // scale self - scalar division
        scaleDown: function(scale) {
            // if scale is zero, then an error is thrown up.  let the app handle that
            this.x /= scale;
            this.y /= scale;
            this.z /= scale;
            this.w /= scale;
            return this;
        },

        // scale self and return as new vector - scalar multiplication
        ScaleDown: function(scale) {
            return new module.Vec4(this.x/scale, this.y/scale, this.z/scale, this.w/scale);
        },

        // dot product of self with a vector
        dot: function(v) {
            return ( this.x*v.x + this.y*v.y + this.z*v.z + this.w*v.w );
        },

        // copy/assignment methods for a vector
        copy: function(v) {
            if ( v !== this ) {
                this.x = v.x;
                this.y = v.y;
                this.z = v.z;
                this.w = v.w;
            }
            return this;
        },

        Copy: function() {
            return new module.Vec4(this.x, this.y, this.z, this.w);
        },
        
		toArray: function() {
			return [this.x, this.y, this.z, this.w].join('');
		},

        log: function() {
			return ['(', this.x, ',', this.y, ',', this.z, ',', this.w, ')'].join("");
        },
        
        // accessor and setter methods
        set:  function(x, y, z, w) { this.x=x; this.y=y; this.z=z; this.w=w; return this; },
        setX: function(x) { this.x = x; return this; },
        setY: function(y) { this.y = y; return this; },
        setZ: function(z) { this.z = z; return this; },
        setW: function(w) { this.w = w; return this; },
        getX: function() { return this.x; },
        getY: function() { return this.y; },
        getZ: function() { return this.z; },
        getW: function() { return this.w; },

	} );  //mLIB.Vec4 = Factory.obj(

	module.Point4 = module.Vec4;	// 4D point defined using the Vec4 class type for now.
									// se Todo note 1. above

    // friend methods ---------------------------------------------
    //
    var friends = {
        Copy: function(v) {
            return new module.Vec4(v.x, v.y, v.z, v.w);
        },
        
        // scaling, return results inline object
        scaleUp: function(scale, v) {
            v.x *= scale;
            v.y *= scale;
            v.z *= scale;
            v.w *= scale;
            return v;
        },

        ScaleUp: function(scale, v) {
            return module.Vec4(scale*v.x, scale*v.y, scale*v.z, scale*v.w);
        },

        // scaling, return results inline object
        scaleDown: function(scale, v) {
            v.x /= scale;
            v.y /= scale;
            v.z /= scale;
            v.w /= scale;
            return v;
        },

        ScaleDown: function(scale, v) {
            return module.Vec4(scale/v.x, scale/v.y, scale/v.z, scale/v.w);
        },

        // adding 2 vectors
        Plus: function(v1, v2) {
            return new module.Vec4(v1.x+v2.x, v1.y+v2.y, v1.z+v2.z, v1.w+v2.w);
        },

        // subtracting vectors
        Minus: function(v1, v2) {
            return new module.Vec4(v1.x-v2.x, v1.y-v2.y, v1.z-v2.z, v1.w-v2.w);
        },
        
        // adding 2 vectors - return into inline object
        plus: function(v1, v2, out) {
            out.x = v1.x+v2.x;
            out.y = v1.y+v2.y;
            out.z = v1.z+v2.z;
            out.w = v1.w+v2.w;
            return out;
        },

        // subtracting vectors - return into inline object
        minus: function(v1, v2, out) {
            out.x = v1.x-v2.x;
            out.y = v1.y-v2.y;
            out.z = v1.z-v2.z;
            out.w = v1.w-v2.w;
            return out;
        },

        dot: function(v1, v2) {
            return (v1.x*v2.x + v1.y*v2.y + v1.y*v2.y + v1.z*v2.z + v1.w*v2.w);
        },

		// check for class type
		isClass: function(theObj, objClass) {
			return Factory.isClass(theObj, objClass);
		},
		
		// return class type of a given object
		getClass: function(theObj) {
			return Factory.getClass(theObj);
		},

        // set up our 4d homogenous space with origin and basis vectors
        origin: new module.Vec4(0.0, 0.0, 0.0, 0.0),
        xAxis:  new module.Vec4(1.0, 0.0, 0.0, 0.0),     // i
        yAxis:  new module.Vec4(0.0, 1.0, 0.0, 0.0),     // j
        zAxis:  new module.Vec4(0.0, 0.0, 1.0, 0.0),     // k
        wAxis:  new module.Vec4(0.0, 0.0, 0.0, 1.0)

    }; // friends{}

    for (var f in friends) {
        friends.hasOwnProperty(f) && (module.Vec4[f] = friends[f]);
    };

} )(mLIB);